package com.eapi.eapigateway;

import com.eapi.eapicommom.model.entity.InterfaceInfo;
import com.eapi.eapicommom.model.entity.User;
import com.eapi.eapicommom.service.InnerInterfaceInfoService;
import com.eapi.eapicommom.service.InnerUserInterfaceInfoService;
import com.eapi.eapicommom.service.InnerUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 全局过滤
 */
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    public static final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1");

    public static final  String INTERFACE_HOST = "localhost:8123";

    @DubboReference(timeout = 100000)
    private InnerUserService innerUserService;
    @DubboReference(timeout = 100000)
    private InnerInterfaceInfoService innerInterfaceInfoService;
    @DubboReference(timeout = 100000)
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 用户发送请求到api
        // 2. 请求日志
        ServerHttpRequest request = exchange.getRequest();
        String path = INTERFACE_HOST + request.getPath().value();
        String method = request.getMethod().toString();
        log.info("请求的唯一标识" + request.getId());
        log.info("请求路径" + path);
        log.info("请求方法" + method);
        log.info("请求参数" + request.getQueryParams());
        log.info("请求请求来源地址" + request.getRemoteAddress());
        String sourceAddress = request.getLocalAddress().getHostString();
        log.info("请求请求来源地址" + sourceAddress);

        // 3.黑白名单
        ServerHttpResponse response = exchange.getResponse();
//        if (!IP_WHITE_LIST.contains(sourceAddress)) {
//            response.setStatusCode(HttpStatus.FORBIDDEN);
//            return response.setComplete();
//        }

        // 4 用户鉴权（判断ak,sk）
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        String timestamp = headers.getFirst("timestamp");
        String sign = headers.getFirst("sign");
        String interfaceInfoId = headers.getFirst("interfaceInfoId");


        // todo 实际应该去数据库中查accessKry是否存在
        User invokeUser = null;
        try {
            invokeUser = innerUserService.getInvokeUser(accessKey);
        }catch (Exception e){
            log.error("getInvokeUser error",e);
        }
        if (invokeUser == null){
            return handleNoAuth(response);
        }
/*        if (!accessKey.equals("mabo")) {
            return handleNoAuth(response);
        }*/
        // todo 实际中nonce应该也是存储起来的，同时会配合时间戳进行定时清除
        /*if (Long.parseLong(nonce) > 100000) {
            return handleNoAuth(response);
            *//*System.out.println("nonce错误");
            throw new RuntimeException("无权限");*//*
            //return "无权限";
        }*/
        // todo 对时间戳进行校验 时间和当前时间不超过五分钟
        Long currentTime = System.currentTimeMillis() / 1000;
        final Long FIVE_MINUTES = 60 * 5L;
        /*if ((currentTime - Long.parseLong(timestamp)) > FIVE_MINUTES) {
            return handleNoAuth(response);
        }*/
        //用和用户端同样的加密方式对请求头中获取的数据进行加密
        //todo 同样的，秘钥也需要到数据库中进行查取
        String secretKey = invokeUser.getSecretKey();
//        String serviceSign = SignUtils.generateSign(timestamp, secretKey);
        /*if (sign == null || !sign.equals(serviceSign)) {
            return handleNoAuth(response);
        }*/


        // 5.判断请求的模拟接口是否存在
        // todo 从数据库中查询模拟接口是否存在，以及请求方式是否匹配，校验请求参数
        // 因为本项目没有引入Mybatis等操作数据库的类库，如果该操作较为复杂，可以有backend项目提供接口，我们直接调用
        // 不用在重复写逻辑： HTTP 请求（用HTTPClient，用RestTemplate)  RPC:Dubbo
        Long id = Long.parseLong(interfaceInfoId);
        InterfaceInfo interfaceInfo = null;
        try {
            interfaceInfo = innerInterfaceInfoService.getInterfaceInfo(id);
        }catch (Exception e){
            log.error("getInterfaceInfo error",e);
        }
        if (interfaceInfo == null) {
            return handleNoAuth(response);
        }

        // todo 是否有调用次数
        // 6. 请求转发，调用模拟接口，响应日志
        Mono<Void> filter = chain.filter(exchange);//放行，找下一个过滤器
        long userId = invokeUser.getId();
        // 8. todo 调用成功，调用接口次数加1 invokecount
        try {
            innerUserInterfaceInfoService.invakeCount(id,userId);
        }catch (Exception e){
            throw new RuntimeException("系统异常");
        }
        return handleResponse(exchange, chain,id,userId);
    }


    /**
     * 处理响应
     *
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain, long interfaceInfoId, long userId) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 缓存数据的工厂
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            // 拿到响应码
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode == HttpStatus.OK) {
                // 装饰，增强能力
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                    // 等调用完转发的接口后才会执行
                    @Override
                    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        log.info("body instanceof Flux: {}", (body instanceof Flux));
                        if (body instanceof Flux) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            // 往返回值里写数据
                            // 拼接字符串
                            return super.writeWith(
                                fluxBody.map(dataBuffer -> {
                                    // 8. todo 调用成功，调用接口次数加1 invokecount
                                    /*try {
                                        innerUserInterfaceInfoService.invakeCount(interfaceInfoId,userId);
                                    }catch (Exception e){
                                        log.error("invokeCount error",e);
                                    }*/
                                    byte[] content = new byte[dataBuffer.readableByteCount()];
                                    dataBuffer.read(content);
                                    DataBufferUtils.release(dataBuffer);//释放掉内存
                                    // 构建日志
                                    StringBuilder sb2 = new StringBuilder(200);
                                    List<Object> rspArgs = new ArrayList<>();
                                    rspArgs.add(originalResponse.getStatusCode());
                                    String data = new String(content, StandardCharsets.UTF_8); //data
                                    sb2.append(data);
                                    // 打印日志
                                    log.info("响应结果：" + data);
                                    return bufferFactory.wrap(content);
                                }));
                            } else{
                                // 8. 调用失败，返回一个规范的错误码
                                log.error("<--- {} 响应code异常", getStatusCode());
                            }
                            return super.writeWith(body);
                        }
                    };
                    // 设置 response 对象为装饰过的
                return chain.filter(exchange.mutate().

                    response(decoratedResponse).

                    build());
                }
                return chain.filter(exchange); // 降级处理返回数据
            } catch(Exception e){
                log.error("网关处理响应异常" + e);
                return chain.filter(exchange);
            }
        }


        @Override
        public int getOrder () {
            return -1;
        }

        public Mono<Void> handleNoAuth (ServerHttpResponse response){
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }

        public Mono<Void> invokeError (ServerHttpResponse response){
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            return response.setComplete();
        }
    }